//----------------------------------------------------------------------------------------
// Name:        process_progress_dialog.cpp
// Purpose:     Describes process_progress_dialog
// Author:      Robert O'Connor
// Modified by:
// Created:     2001/10/20
// Copyright:   (c) Robert O'Connor ( www.oconnor.md )
// Licence:     GPL
// RCS-ID:      $Id: process_progress_dialog.cpp,v 1.12 2007/06/29 00:58:55 robertoconnor Exp $
//----------------------------------------------------------------------------------------

//----------------------------------------------------------------------------------------
// GCC implementation
//----------------------------------------------------------------------------------------

#if defined(__GNUG__) && ! defined(__APPLE__)
    #pragma implementation "process_progress_dialog.h"
#endif

//----------------------------------------------------------------------------------------
// Setup information
//----------------------------------------------------------------------------------------

#include "setup.h"

//----------------------------------------------------------------------------------------
// Begin feature removal condition
//----------------------------------------------------------------------------------------

#if ( setupUSE_ENHANCED_PROGRESS_DIALOG )

//----------------------------------------------------------------------------------------
// Standard wxWindows headers
//----------------------------------------------------------------------------------------

// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

// For all others, include the necessary headers (this file is usually all you
// need because it includes almost all "standard" wxWindows headers)
#ifndef WX_PRECOMP
    #include "wx/wx.h"
#endif

//----------------------------------------------------------------------------------------
// Header of this .cpp file
//----------------------------------------------------------------------------------------

#include "process_progress_dialog.h"

//----------------------------------------------------------------------------------------
// Remaining headers: Needed wx headers, then wx/contrib headers, then application headers
//----------------------------------------------------------------------------------------

#include "wx/txtstrm.h"
#include "wx/process.h"
#include "wx/confbase.h"
#include "wx/fileconf.h"

// ---------------------------------------------------------------------------------------

#include "wx/xrc/xmlres.h"          // XRC XML resouces

//----------------------------------------------------------------------------------------
// Internal constants: control IDs
//----------------------------------------------------------------------------------------

// I am worried about timer conflicts with main frame (perhaps paranoia)
enum {
    ID_PROCESS_PROGRESS_TIMER = wxID_HIGHEST + 10
};

//----------------------------------------------------------------------------------------
// wxWindows macro: implement dynamic class
//----------------------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( process_progress_dialog, details_progress_dialog )

//----------------------------------------------------------------------------------------
// Event table: connect the events to the handler functions to process them
//----------------------------------------------------------------------------------------

BEGIN_EVENT_TABLE( process_progress_dialog, details_progress_dialog )
    // A dummy control that just handles the termination event generated by the process.
    EVT_END_PROCESS ( XRCID( "on_process_terminated_dummy_control" ), process_progress_dialog::on_process_terminated )
    EVT_IDLE( process_progress_dialog::on_idle )
#if ( setupUSE_PROCESS_PROGRESS_DIALOG_IDLE_TIMER )
    // The force new idle event timer
    EVT_TIMER( ID_PROCESS_PROGRESS_TIMER, process_progress_dialog::on_timer )
#endif
END_EVENT_TABLE()

//----------------------------------------------------------------------------------------
// Public methods
//----------------------------------------------------------------------------------------

process_progress_dialog::process_progress_dialog( wxWindow* parent,
                                  const wxArrayString& commandline_array,
                                  long stream_kind,
                                  wxSignal kill_signal,
                                  bool automatically_close_when_done,
                                  const wxString& show_hide_listbox_configuration_key,
                                  const wxString& log_filename,
                                  const wxString& log_path,
                                  long scroll_history
                                )
                                : details_progress_dialog( parent,
                                  automatically_close_when_done,
                                  show_hide_listbox_configuration_key,
                                  log_filename,
                                  log_path,
                                  scroll_history )

{
    //---Store things from the constructor------------------------------------------------

    // Store commandline array...
    m_commandline_array         = commandline_array;
    // ...and options....
    m_stream_kind               = stream_kind;
    // ...and the kill signal...
    m_kill_signal               = kill_signal;

#if ( setupUSE_PROCESS_PROGRESS_DIALOG_IDLE_TIMER )
    // NOTE: for Borland C++ anyway, (and Visual C I think gives a warning), can't do
    // a m_idle_timer( this ) in the member constructor list. Borland C++ causes wxWindows
    // memory pointer trashing on program close. Therefore, just let it use its default
    // constructor, and then call SetOwner to set it to 'this'.
    wxLogDebug( wxT( "Setting owner of m_idle_timer" ) );
    m_idle_timer.SetOwner( this, ID_PROCESS_PROGRESS_TIMER );
    wxLogDebug( wxT( "Set owner of m_idle_timer" ) );
#endif

}


process_progress_dialog::~process_progress_dialog()
{
    // Free memory used by the array.
    wxLogDebug( wxT( "About to clear commandline_array()" ) );
    m_commandline_array.Clear();
}


// Recall this is virtual. It can be overriden with more interesting things that this.
void process_progress_dialog::on_process_generated_an_output_line( long stream_kind,
                                          const wxString& line_text,
                                          size_t commandline_array_index
                                        )
{
    get_details_listbox()->append_using_scroll_history( line_text );
}

//-----Process functions-----------------------------------------------------------

void process_progress_dialog::action_to_do_when_dialog_is_ready()
{
    wxLogDebug( wxT( "Entering process_progress_dialog::action_to_do_when_dialog_is_ready()" ) );

    if ( m_commandline_array.GetCount() == 0 )
    {
        wxLogDebug( wxT( "process_progress_dialog::action_to_do_when_dialog_is_ready(): m_commandline_array empty. Aborting." ) );
        return;
    }

    // Abort function if a desire to close dialog was entered.
    if ( get_abort_signal_was_entered() )
    {
        return;
    }

    // Do the virtual commands that we want done before executing the array.
    on_before_execute_commandline_array();

    // Abort function if a desire to close dialog was entered.
    if ( get_abort_signal_was_entered() ) 
    {
        return;
    }

#if ( setupUSE_PROCESS_PROGRESS_DIALOG_IDLE_TIMER )
    // Start the timer (milliseconds)
    wxLogDebug( wxT( "Starting timer" ) );
    m_idle_timer.Start( 2000 );
#endif

    // Set the first commanline array index to zero...
    m_current_commandline_array_index = 0;
    // ..and then execute the first commandline.
    execute_commandline_array_item( m_current_commandline_array_index );
}


void process_progress_dialog::action_to_do_before_dialog_finishes_closing()
{
    // Kill the active process if there is one currently going.
    //! \test Does this call its EVT_PROCESS_TERMINATED?
    detach_and_kill_all_running_processes();

#if ( setupUSE_PROCESS_PROGRESS_DIALOG_IDLE_TIMER )
    // If the timer is running...
    if ( m_idle_timer.IsRunning() ) 
    {
        // ...stop it
        wxLogDebug( wxT( "About to stop process_progress_dialog's m_idle_timer" ) );
        m_idle_timer.Stop();
    }
#endif

}


void process_progress_dialog::action_to_do_after_dialog_finishes_closing()
{
}


void process_progress_dialog::set_stream_kind( long stream_kind )
{
    m_stream_kind = stream_kind;
}


long process_progress_dialog::get_stream_kind()
{
    return m_stream_kind;
} 


size_t process_progress_dialog::get_current_commandline_array_index()
{
    return m_current_commandline_array_index;
}

//----------------------------------------------------------------------------------------
// Private methods
//----------------------------------------------------------------------------------------

void process_progress_dialog::detach_and_kill_all_running_processes() 
{
    wxLogDebug( wxT( "Entering process_progress_dialog::detach_and_kill_all_running_processes()" ) );
    
    // Check if there is a process still running, by checking our member variable 
    // m_is_process_running. Note that we use this way instead of 
    // wxProcess::Exists( m_most_recent_pid ) for 2 reasons:
    // (1) If our process ended with that pid and another program started up a process
    // and picked the now available pid, we would then kill another programs process.
    // (2) Borland BCC [on wx231 anyways] asserts on wxProcess::Exists( int pid ) 
    // anyways.
    
    for ( size_t running_process_array_index = 0; 
          running_process_array_index < m_running_process_array.GetCount();
          running_process_array_index++ ) 
    {
        int pid_to_kill = m_running_process_array[ running_process_array_index ]->get_process_pid();
        
        wxLogDebug( wxT( "pid to detach and kill=%d" ), pid_to_kill );               
        
        // Detach the process from its parent. We don't want anymore events
        // raised. Instead it should just silently delete itself.
        wxLogDebug( wxT( "Detaching process by calling Detach()..." ) );
        m_running_process_array[ running_process_array_index ]->Detach();       
        
        // A variable to hold the error number.
        wxKillError kill_error_number;       
        
        // As John Grisham would say, A Time To Kill.
        wxLogDebug( wxT( "Killing process..." ) );
        
#if ( setupUSE_MSW_ENHANCED_PROCESS_KILLING )

        // Use a custom overriden virtual to kill.
        custom_kill( pid_to_kill, m_kill_signal, &kill_error_number );
                                          	
#else   // setupUSE_MSW_ENHANCED_PROCESS_KILLING

        // On the Unix variants, the stock wxKill already does what we want.
        // Recall to that we set the kill children flag on the wxExecute,
        // since that is how it works on POSIX.
        wxKill( pid_to_kill, m_kill_signal, &kill_error_number );
        
#endif  // setupUSE_MSW_ENHANCED_PROCESS_KILLING

        // ...And put a log message of what the error number was. 
        wxLogDebug( wxT( "Kill attempt of process pid=%d (on MSW=%u) returned code=%d" ), 
                     pid_to_kill, (unsigned)pid_to_kill, kill_error_number );
        
        // And print out a guide of error numbers. 
        // Not using the wxExecute example way since I believe BCC was able to 
        // spit out -4's?
        /*! 
            \todo Look to see if wxKill can return something on BCC that is outside
                the boundaries of the array which would cause character array to crash
                on the wxExecute sample.    
         */
             
        wxString error_code_key;
        error_code_key << wxT( "Usual codes are:\n " )
                       << wxT( "0=No error,\n " )
                       << wxT( "1=Signal not supported,\n " )       
                       << wxT( "2=Permission denied,\n " )
                       << wxT( "3=No such process,\n " )
                       << wxT( "4=Unknown ending message" );
        wxLogDebug( error_code_key );
    }
}


void process_progress_dialog::execute_commandline_array_item( size_t commandline_array_index )
{
    wxLogDebug( wxT( "Entering process_progress_dialog::execute_commandline_array_item()" ) );
       
    // Set the member current array index, so can be sent out as part of
    // on_process_generated_an_output_line() .
    m_current_commandline_array_index = commandline_array_index;
    
    // Do the virtual commands that we want done before execute this commandline. 
    on_before_execute_commandline_array_item( m_current_commandline_array_index );

    // Abort function if a desire to close dialog was entered.
    if ( get_abort_signal_was_entered() ) 
    {
        return;
    }

    // Get the commandline from our array.
    wxString commandline = m_commandline_array.Item( commandline_array_index );

    // Make a new piped process. Its parent is 'this' dialog and the ID of the control
    // that will recieve its events is our dummy control. If we abort, then we will
    // Detach() the process from this control ID, so that no more events are sent from
    // the process to that control.
    piped_process *a_piped_process = new piped_process( this, XRCID( "on_process_terminated_dummy_control" ) );

    // Now Execute it with our flags, catching the pid of the new process
    // wxEXEC_MAKE_GROUP_LEADER kills children of process on Unix.
    int newly_created_pid = ::wxExecute( commandline, wxEXEC_ASYNC | wxEXEC_MAKE_GROUP_LEADER, a_piped_process );

    // The returned pid is 0 if something when wrong.
    if ( newly_created_pid == 0 ) 
    {
        wxLogDebug( wxT( "Execution of '%s' failed." ), commandline.c_str() );
        // Delete it.
        delete a_piped_process;
    } 
    else 
    {
        // Add to our array of running processes.
        m_running_process_array.Add( a_piped_process );
        // Store its pid into it, so that can find it if want to kill it by pid.
        a_piped_process->set_process_pid( newly_created_pid );
        // Show a debug message of the most recent pid.
        // MSW can have long unsigned as process ids, so make the MSW more readable,
        /// and also avoid GCC warning that we have mismatched unsigned and long unsigned.
        wxLogDebug( wxT( "Started execution of %s. pid=%d (on MSW=%u)" ),
                    commandline.c_str(), newly_created_pid, (unsigned)newly_created_pid );
    }

}


void process_progress_dialog::on_idle( wxIdleEvent& event )
{
    wxLogDebug( wxT( "Entering process_progres_dialog::on_idle" ) );

    for ( size_t running_process_array_index = 0;
          running_process_array_index < m_running_process_array.GetCount();
          running_process_array_index++ ) 
    {

        // If there wasn't an abort signal entered [This is FUNDAMENTAL]....
        if ( ! get_abort_signal_was_entered() &&
            /// ...suck some process output returning whether or not there is more input queued...
            m_running_process_array[ running_process_array_index ]->suck_process_output( FALSE ) )
        {
            // ...and if there is, then suck it again.
            event.RequestMore();
        }

    }

    // Apparently, supposed to do an event.Skip(). Esp for treectrl's and
    // others that need the idle event?
    // Doesn't seem to do much though (and allegedly makes things slower).
    event.Skip();
}


void process_progress_dialog::on_process_terminated( wxProcessEvent& event )
{

    wxLogDebug( wxT( "process_progress_dialog::on_process_terminated: pid=%u terminated." ),
                event.GetPid() );

    for ( size_t running_process_array_index = 0; 
          running_process_array_index < m_running_process_array.GetCount();
          running_process_array_index++ )
    {   
                   
        // Do a while loop show the rest of the output (will stop when it returns
        // false, that ther is no more input.
        while ( m_running_process_array[ running_process_array_index ]->suck_process_output( TRUE ) )
        
            ; // \todo Safe to put brackets around here?

        // we're not needed any more
        wxLogDebug( wxT( "process_progress_dialog::on_process_terminated: About to delete process." ) );
        delete m_running_process_array[ running_process_array_index ];
        wxLogDebug( wxT( "process_progress_dialog::on_process_terminated: deleted process." ) );

        // Note: if removed from an object array, Remove() would delete it also. 
        // But in a normal array of ints/pointers, nothing happens: need to delete
        // it manuallly.
        wxLogDebug( wxT( "Entering process_progress_dialog::on_process_terminated()" ) );
        m_running_process_array.Remove( m_running_process_array[ running_process_array_index ] );
    }
    
    // Abort function (and hence further process executions, since the terminate 
    // events are chained to the start of the next execution.
    if ( get_abort_signal_was_entered() ) 
    {
        return;
    }
        
    // Do the virtual commands that we want done after execute this commandline.     
    wxLogDebug( wxT( "Doing process_progress_dialog::on_after_commandline_array_item_execution_finished()..." ) ); 
    on_after_commandline_array_item_execution_finished( m_current_commandline_array_index );
          
    // Abort function (and hence further process executions, since the terminate
    // events are chained to the start of the next execution.
    if ( get_abort_signal_was_entered() ) 
    {
        return;
    }

    // ...If abort signal wasn't entered, then initiate the next process:
    // If we are not at the end of the commandline_array yet
    // [ remember the -1 in the GetCount() ]...
    if ( (int)m_current_commandline_array_index < (int)m_commandline_array.GetCount()-1 ) {
        // ...then increase the commanline array index by 1...
        m_current_commandline_array_index++;
        // ..and then execute this commandline.
        execute_commandline_array_item( m_current_commandline_array_index );
    } 
    else 
    {
        // The before array is done, the before process / process / after processes are
        // all done, all that is left is the stuff aftet complete the array:
        on_after_commandline_array_executions_finished();
        // And now just set some things to mark the dialog gotten all the way through.
        all_chores_completed_without_interruption();
    }

}


// virtual. Override as needed.
void process_progress_dialog::custom_kill( long pid, wxSignal sig, wxKillError *krc )
{

}


#if ( setupUSE_PROCESS_PROGRESS_DIALOG_IDLE_TIMER )
void process_progress_dialog::on_timer( wxTimerEvent& event )
{
    wxLogDebug( wxT( "Entering process_progress_dialog::on_timer()" ) );
    // Force an idle event to be generated
    // This will execute the stuff in on_idle().
    wxWakeUpIdle();
}
#endif

//----------------------------------------------------------------------------------------
// End feature removal condition
//----------------------------------------------------------------------------------------

#endif  // setupUSE_ENHANCED_PROGRESS_DIALOG
